<?php


namespace Module\AigcChatZhipuAi\Core;


use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use ModStart\Core\Input\Response;
use ModStart\Core\Util\CurlUtil;
use ModStart\Core\Util\HtmlUtil;
use ModStart\Core\Util\SerializeUtil;
use Module\AigcBase\Provider\AbstractAigcChatProvider;
use Module\AigcBase\Util\AigcKeyPoolUtil;
use Module\Vendor\Markdown\MarkdownUtil;

class ZhipuAiAigcChatProvider extends AbstractAigcChatProvider
{
    const NAME = 'zhipu';

    public function name()
    {
        return self::NAME;
    }

    public function title()
    {
        return '智谱AI';
    }

    public function help()
    {
        return '开通网址：https://open.bigmodel.cn/ <a href="https://open.bigmodel.cn/" target="_blank" rel="noopener noreferrer">前往开通</a>';
    }


    public function chatStream($streamCallback, $sessionId, $msg, $option = [])
    {
        $msg = $this->normalMsg($msg);
        if ($msg['type'] != 'text') {
            return Response::generateError('机器人不能识别消息，请稍后再试');
        }
        $content = HtmlUtil::text($msg['content']);
        if (empty($content)) {
            return Response::generateError('消息内容为空');
        }
        $key = AigcKeyPoolUtil::randomByType(self::NAME);
        if (!$key) {
            return Response::generateError('AI没有配置');
        }

        $url = 'https://open.bigmodel.cn/api/paas/v4/chat/completions';

        $data = [];
        $data['model'] = modstart_config('AiChatZhipuAi_Model');
        $data['messages'] = [
            [
                'role' => 'user',
                'content' => $content,
            ]
        ];
        $data['stream'] = true;
        $data['temperature'] = floatval(isset($param['temperature']) ? $param['temperature'] : 0.9);

        $option = [];
        $option['header'] = [
            'Content-Type' => 'application/json',
            'Authorization' => "Bearer " . $this->getToken($key['param']['key']),
        ];
        $option['timeout'] = 60 * 10;

        $resultContent = '';
        $buffer = '';
        $option['writeFunctionCallback'] = function ($data, $param) use (&$buffer, &$streamCallback, &$resultContent) {
            $buffer .= $data;
            if (Str::contains($buffer, "\n\n")) {
                $lines = explode("\n\n", $buffer);
                $buffer = array_pop($lines);
                foreach ($lines as $line) {
                    // data: {"id":"8426537302949101672","created":1709114765,"model":"chatglm_std","choices":[{"index":0,"delta":{"role":"assistant","content":" "}}]}
                    $lineText = substr($line, 6);
                    if ($lineText == '[DONE]') {
                        $payload = [
                            'type' => 'end',
                        ];
                        call_user_func_array($streamCallback, [$payload, []]);
                        return;
                    }
                    $json = SerializeUtil::jsonDecode($lineText);
                    if (!empty($json['choices'][0]['delta']['content'])) {
                        $content = $json['choices'][0]['delta']['content'];
                        $resultContent .= $content;
                        $payload = [
                            'type' => 'data',
                            'data' => $content,
                        ];
                        call_user_func_array($streamCallback, [$payload, []]);
                    }
                }
            }
        };
        try {
            $ret = CurlUtil::post($url, SerializeUtil::jsonEncode($data), $option);
            Log::info('AigcChatZhipiAi.ChatStream - ' . SerializeUtil::jsonEncode($data) . ' - ' . SerializeUtil::jsonEncode($ret) . ' - ' . SerializeUtil::jsonEncode($resultContent));
            $texts = MarkdownUtil::convertToHtml($resultContent);
            return Response::generateSuccessData([
                'msg' => [
                    'type' => 'text',
                    'content' => $texts,
                ]
            ]);
        } catch (\Exception $e) {
            Log::info('AigcChatZhipiAi.ChatStream.Error - ' . $e->getMessage());
            return Response::generateSuccessData([
                'msg' => [
                    'type' => 'text',
                    'content' => '机器人太忙啦，请稍后再试'
                ]
            ]);
        }
    }

    public function chat($sessionId, $msg, $option = [])
    {
        $msg = $this->normalMsg($msg);
        if ($msg['type'] != 'text') {
            return Response::generateError('机器人不能识别消息，请稍后再试');
        }
        $content = HtmlUtil::text($msg['content']);
        if (empty($content)) {
            return Response::generateError('消息内容为空');
        }
        $key = AigcKeyPoolUtil::randomByType(self::NAME);
        if (!$key) {
            return Response::generateError('AI没有配置');
        }

        $url = 'https://open.bigmodel.cn/api/paas/v4/chat/completions';

        $data = [];
        $data['model'] = modstart_config('AiChatZhipuAi_Model');
        $data['messages'] = [
            [
                'role' => 'user',
                'content' => $content,
            ]
        ];
        $data['temperature'] = floatval(isset($param['temperature']) ? $param['temperature'] : 0.9);

        $option = [];
        $option['header'] = [
            'Content-Type' => 'application/json',
            'Authorization' => "Bearer " . $this->getToken($key['param']['key']),
        ];
        $option['timeout'] = 60 * 10;

        try {
            $ret = CurlUtil::post($url, SerializeUtil::jsonEncode($data), $option);
            Log::info('AigcChatZhipiAi.Chat - ' . SerializeUtil::jsonEncode($ret) . ' - ' . SerializeUtil::jsonEncode($ret));
            $json = @json_decode($ret['body'], true);
            $content = trim($json['choices'][0]['message']['content']);
            $content = MarkdownUtil::convertToHtml($content);
            AigcKeyPoolUtil::markSuccess($key);
            return Response::generateSuccessData([
                'msg' => [
                    'type' => 'text',
                    'content' => $content,
                ]
            ]);
        } catch (\Exception $e) {
            Log::info('AigcChatZhipiAi.Chat.Error - ' . $e->getMessage());
            AigcKeyPoolUtil::markFail($key);
            return Response::generateSuccessData([
                'msg' => [
                    'type' => 'text',
                    'content' => '机器人太忙啦，请稍后再试'
                ]
            ]);
        }
    }

    private function getToken($key)
    {
        list($id, $secret) = explode('.', $key);
        $payload = [
            'api_key' => $id,
            'exp' => (time() + 3600) * 1000,
            'timestamp' => time() * 1000,
        ];

        $header = [
            "alg" => "HS256",
            "sign_type" => "SIGN"
        ];
        return $this->jwt_encode($payload, $secret, "sha256", $header);
    }

    private function jwt_encode($payload, $key, $algorithm, $header)
    {
        $header_encoded = $this->base64url_encode(json_encode($header));
        $payload_encoded = $this->base64url_encode(json_encode($payload));

        $data = $header_encoded . "." . $payload_encoded;
        $signature = hash_hmac($algorithm, $data, $key, true);
        $signature_encoded = $this->base64url_encode($signature);
        return $data . "." . $signature_encoded;
    }

    private function base64url_encode($data)
    {
        $base64 = base64_encode($data);
        $base64url = strtr($base64, '+/', '-_');
        return rtrim($base64url, '=');
    }

}
